/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/*
 * PanWriting.java
 *
 * Created on 22-abr-2011, 0:19:57
 */
package br.usp.ime.faguilar.guis.MathExpressionDrawing.Panels;

import br.usp.ime.faguilar.guis.capturers.PanControlInputMathExpressions;
import br.usp.ime.faguilar.guis.MathExpressionDrawing.Drawables.AreaSquares;
import MathExpression.Graphics.GMathExpression;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyListener;
import java.awt.geom.Point2D;
import javax.swing.Timer;
import MathExpression.Graphics.GSymbol;
import MathExpression.Data.DSymbol;
import MathExpression.Data.TimePoint;
import MathExpression.Graphics.EditableMathExpression;
import Util.History;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.util.Calendar;
import javax.swing.JTextField;

/**
 *
 * @author Frank
 *
 * This class is used as the panel(area) on which users can write mathematical
 * expressions
 */
public class PanWriting extends MathExpressionsDrawingPanel implements ActionListener,
KeyListener {

    /**
     * Timer to measure the time passed since the user wrote the last stroke
     */
    private Timer timerForLastWrittenStroke;
    /**
     * Timer to update advanced bar or other GUI
     */
    private Timer timerToUpdateAdvanceBar;

    private static final int numberOfUpdates=20;
    /**
     * Time in which a user can write strokes of the same symbol
     */
    private int timeBetweenStrokes;

    private History history;

    protected int state;

    public static final int STATE_WRITING=0;

    public static final int STATE_LABELING=1;

    public static final int STATE_DELETING_SYMBOL=2;

    public static final int STATE_DO_NOTHING=3;

//    protected DSymbol dSymbolToBeLabeled;
    protected int positionOfSymbolToBeLabeled;

    protected JTextField txtFLabel;

    protected static final int LABELING_TEXT_FIELD_WIDTH=60;
    protected static final int LABELING_TEXT_FIELD_HEIGHT=28;

    private PanControlInputMathExpressions panControl;

    private int timerAdvance;

    private boolean drawSquares;

    private AreaSquares areaSquares;

    private static final int SIZE_INCREMENT=20;
    
    private static final int DEFAULT_TIME_BETWEEN_SYMBOL_STROKES=1000;
    /** Creates new form PanWriting */
    public PanWriting() {
        initComponents();
        initMyAttributes();
    }

    public int getSquaresDimension() {
        return this.areaSquares.getSquaresDimension();
    }

    public void setSquaresDimension(int squaresDimension) {
        this.areaSquares.setSquaresDimension(squaresDimension);
    }
    /**
     * Initialize all our attributes.
     */
    private void initMyAttributes() {
        setAdaptExpressionToPanelSize(false);
        drawable=new EditableMathExpression();
        drawSquares=true;
        timeBetweenStrokes = DEFAULT_TIME_BETWEEN_SYMBOL_STROKES;
        timerForLastWrittenStroke = new Timer(timeBetweenStrokes, this);
        timerToUpdateAdvanceBar=new Timer(timeBetweenStrokes/numberOfUpdates, this);
        txtFLabel=null;
        history=new History();
        this.addCurrcentExpressionToHistory();
        panControl=null;
        timerAdvance=0;
        areaSquares=new AreaSquares();
        setAutoscrolls(true);
    }

    public PanControlInputMathExpressions getPanControl() {
        return panControl;
    }

    public void setPanControl(PanControlInputMathExpressions panControl) {
        this.panControl = panControl;
    }
    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        setBackground(new java.awt.Color(255, 255, 204));
        addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                formMousePressed(evt);
            }
            public void mouseReleased(java.awt.event.MouseEvent evt) {
                formMouseReleased(evt);
            }
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                formMouseClicked(evt);
            }
        });
        addMouseMotionListener(new java.awt.event.MouseMotionAdapter() {
            public void mouseDragged(java.awt.event.MouseEvent evt) {
                formMouseDragged(evt);
            }
        });
        addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyTyped(java.awt.event.KeyEvent evt) {
                formKeyTyped(evt);
            }
        });

        org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(0, 400, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(0, 300, Short.MAX_VALUE)
        );
    }// </editor-fold>//GEN-END:initComponents

    /**
     * User starts to wrtie a stroke
     * @param evt
     */
    private void formMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_formMousePressed
        // TODO add your handling code here:
        if(this.getState()==PanWriting.STATE_WRITING){
            ((EditableMathExpression)drawable).resetNewStroke();
            this.addPointToCurrentStroke(evt.getPoint());
            if (((EditableMathExpression)drawable).isCurrentSymbolEmty()) {
                //the stroke is part of a new symbol
                ((EditableMathExpression)drawable).resetNewSymgol();
            } else {
                resetTimersAndAdvance();
            }
        }
    }//GEN-LAST:event_formMousePressed

    /**
     * As the user moves the mouse, the coordenates are added
     * to the current stroke
     * @param evt
     */
    private void formMouseDragged(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_formMouseDragged
        // TODO add your handling code here:
        if(this.getState()==PanWriting.STATE_WRITING){
            this.addPointToCurrentStroke(evt.getPoint());
        }
    }//GEN-LAST:event_formMouseDragged

    public void beforeExpressMatch(){
        if(history.hasPreviousElement()){
            drawable=(EditableMathExpression) ((EditableMathExpression) history.previousElement()).clone();
            ((EditableMathExpression)drawable).resetCurrentAttributes();
            repaint();
        }
    }

    public void nextExpressMatch(){
        if(history.hasNextElement()){
            drawable=(EditableMathExpression) ((EditableMathExpression) history.nextElement()).clone();
            ((EditableMathExpression)drawable).resetCurrentAttributes();
            repaint();
        }
    }
    /**
     * When user finishes the writing of a stroke, it is added to the
     * current symbol
     * @param evt
     */
    private void formMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_formMouseReleased
        // TODO add your handling code here:
        if(this.getState()==PanWriting.STATE_WRITING){
            restartTimersAndAdvance();
            updateAdvanceInControlPanel();
            ((EditableMathExpression)drawable).includeCurrentStroke();
            EditableMathExpression expressionForHistory = (EditableMathExpression) ((EditableMathExpression)drawable).clone();
            ((EditableMathExpression)expressionForHistory).includeCurrentSymbol();
            addExpressionToHistory(expressionForHistory);
            adaptPanelToExpression();
            this.repaint();
        }
    }//GEN-LAST:event_formMouseReleased

    private void addExpressionToHistory(EditableMathExpression expressionForHistory){
        history.addRemovingNexts(expressionForHistory);
    }

    private void saveMathExpressionInHistory(){
        EditableMathExpression expressionForHistory=(EditableMathExpression) ((EditableMathExpression)drawable).clone();
        history.addRemovingNexts(expressionForHistory);
    }

    private void updateAdvanceInControlPanel(){
        this.panControl.setAdvanceBarValue(Math.min(timerAdvance, timeBetweenStrokes),timeBetweenStrokes );
    }
    private void restartTimersAndAdvance(){
         timerAdvance=0;
         timerToUpdateAdvanceBar.restart();
         this.timerForLastWrittenStroke.restart();
    }


    private void formMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_formMouseClicked
        // TODO add your handling code here:
        if(this.getState()==PanWriting.STATE_LABELING){
            Point2D p=evt.getPoint();
            int posSymbol = getPositionOfSymbolFromPoint(p);
            if(posSymbol!=-1){
                setUpTextFieldToLabel(posSymbol);
            }else if(this.txtFLabel!=null){
                    txtFLabel.setVisible(false);
            }
        }
         else if(this.getState()==PanWriting.STATE_DELETING_SYMBOL){
            Point2D p=evt.getPoint();
            int posSymbol=((EditableMathExpression)drawable).containsPoint(p);
            if(posSymbol!=-1){
                drawable= ((EditableMathExpression)drawable).getACopyWithout(posSymbol);
                ((EditableMathExpression)drawable).resetNewStroke();
                saveMathExpressionInHistory();
            }
            this.repaint();
         }
    }//GEN-LAST:event_formMouseClicked

    private void formKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_formKeyTyped
        // TODO add your handling code here:
    }//GEN-LAST:event_formKeyTyped

    private int getPositionOfSymbolFromPoint(Point2D p){
        return ((EditableMathExpression)drawable).containsPoint(p);
    }

    private void setUpTextFieldToLabel(int posSymbol){
        if(this.txtFLabel!=null){
                    txtFLabel.setVisible(false);
                }
                txtFLabel=new JTextField("");

                DSymbol tempGS=((EditableMathExpression)drawable).get(posSymbol);
                Point2D positionTxtField=this.calculateTextFieldPosition((GSymbol) tempGS,LABELING_TEXT_FIELD_WIDTH, LABELING_TEXT_FIELD_HEIGHT);
                txtFLabel.setBounds((int)positionTxtField.getX(),
                        (int)positionTxtField.getY(), LABELING_TEXT_FIELD_WIDTH, LABELING_TEXT_FIELD_HEIGHT);
//                txtFLabel.addActionListener(this);
                txtFLabel.addKeyListener(this);
                this.add(txtFLabel);

//                dSymbolToBeLabeled=((EditableMathExpression)drawable).get(posSymbol);
                positionOfSymbolToBeLabeled = posSymbol;
                this.repaint();
                txtFLabel.requestFocusInWindow();
    }
    private Point2D calculateTextFieldPosition(GSymbol s, int txtFieldWidth,
            int txtFieldHeight){
        Point2D position=new Point2D.Double();
        double posX=s.getCenterOfBBoc().getX()-txtFieldWidth/2.;
        double posY=s.getCenterOfBBoc().getY()-(txtFieldHeight+s.getHeight()/2);
        position.setLocation(posX, posY);
        return position;
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    // End of variables declaration//GEN-END:variables
    /**
     * adds a point to the current stroke
     * @param p
     */
    private void addPointToCurrentStroke(Point2D p) {
        long timeInSeconds=Calendar.getInstance().getTime().getTime();
        TimePoint timePoint=new TimePoint(p.getX(), p.getY(), timeInSeconds);
        ((EditableMathExpression)drawable).addPointToCurrentStroke(timePoint);
        this.repaint();
    }
    protected void draw(Graphics g){
        this.drawSquares(g);
        super.draw(g);
    }

    public boolean isDrawSquares() {
        return drawSquares;
    }

    public void setDrawSquares(boolean drawSquares) {
        this.drawSquares = drawSquares;
    }

    public void drawSquares(Graphics g){
        areaSquares.drawSquares(g, this.getSize());
    }

    public void setUpNextLabeling(){
        int position = getNextPositionOfDSymbolToLabel();
            if(position>=0)
                setUpTextFieldToLabel(position);
    }

    /**
     * When timerForLastWrittenStroke finishes, calls this function. The system considers
     * that the user finished writing all the strokes of the current symbol
     * @param e
     */
    public void actionPerformed(ActionEvent e) {
        Object source=e.getSource();
        if(timerToUpdateAdvanceBar== source){
            incrementAdvance();
            updateAdvanceInControlPanel();
        }else if(timerForLastWrittenStroke == source &&
                this.getState()==PanWriting.STATE_WRITING){
                ((EditableMathExpression)drawable).includeCurrentSymbol();
                ((EditableMathExpression)drawable).resetCurrentAttributes();
                completeAdvance();
                updateAdvanceInControlPanel();
                resetTimersAndAdvance();
                this.repaint();
        }
    }

    private void completeAdvance(){
        timerAdvance=timeBetweenStrokes;
    }

    private void incrementAdvance(){
        timerAdvance+=(timeBetweenStrokes/numberOfUpdates);
    }

    private void resetTimersAndAdvance(){
        this.timerForLastWrittenStroke.stop();
        this.timerToUpdateAdvanceBar.stop();
    }

    /**
     * Clears te panel.
     */
    public void clearPanel() {
            drawable=new EditableMathExpression();
            this.repaint();
    }

    private void addCurrcentExpressionToHistory(){
        this.history.addRemovingNexts(((EditableMathExpression)drawable).clone());
    }

    public GMathExpression getgMathExpressionWritten() {
        return ((EditableMathExpression)drawable);
    }

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
        if(state==STATE_LABELING){
            setUpNextLabeling();
        }
    }

    public int getTimeBetweenStrokes() {
        return timeBetweenStrokes;
    }

    protected void adaptPanelToExpressionSize(){
        int newWidth=this.getWidth();
        int newHeight=this.getHeight();
        Point2D p=this.getgMathExpressionWritten().getRbPoint();
        if(tooThin(p))
            newWidth=(int)(p.getX()+SIZE_INCREMENT);
        if(tooShort(p))
            newHeight=(int)(p.getY()+SIZE_INCREMENT);
        this.setPreferredSize(new Dimension(newWidth,newHeight));
        Rectangle r=new Rectangle((int)p.getX()+SIZE_INCREMENT,
                (int)p.getY()+SIZE_INCREMENT,1,1);
        panControl.updateScroll(r);
    }

    protected void adaptPanelToExpression(){
        Point2D p=((EditableMathExpression)drawable).getTotalRightBottom();//this.getgMathExpressionWritten().getRbPoint();
        if(tooShort(p)||tooThin(p))
            adaptPanelToExpressionSize();
    }

    private int  getNextPositionOfDSymbolToLabel(){
        int position=-1;
        int cont = -1;
        EditableMathExpression editableExpression= ((EditableMathExpression)drawable);
        for (DSymbol dSymbol : editableExpression) {
            cont++;
            if(dSymbol.getLabel().isEmpty()){
                position = cont;
                break;
            }
        }
        return position;
    }

    private boolean tooThin(Point2D p){
        return p.getX()>(this.getWidth()-SIZE_INCREMENT);
    }

    private boolean tooShort(Point2D p){
        return p.getY()>(getHeight()-SIZE_INCREMENT);
    }
    public void setTimeBetweenStrokes(int timeBetweenStrokes) {
        this.timeBetweenStrokes = timeBetweenStrokes;
        timerForLastWrittenStroke = new Timer(timeBetweenStrokes, this);
        timerToUpdateAdvanceBar=new Timer(timeBetweenStrokes/numberOfUpdates, this);
    }

    public int getTimerAdvance() {
        return timerAdvance;
    }

    public void setTimerAdvance(int timerAdvance) {
        this.timerAdvance = timerAdvance;
    }

    public void keyTyped(KeyEvent ke) {
//        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void keyPressed(KeyEvent ke) {
//        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void keyReleased(KeyEvent ke) {
//        throw new UnsupportedOperationException("Not supported yet.");
        if(ke.getKeyCode() == KeyEvent.VK_ENTER &&
                this.getState()==PanWriting.STATE_LABELING){
                JTextField txtF=(JTextField)ke.getSource();
                String label=txtF.getText();
                ((EditableMathExpression)drawable).setSymbolLabel(positionOfSymbolToBeLabeled, label);
//                this.dSymbolToBeLabeled.setLabel(label);
                ((GSymbol)((EditableMathExpression)drawable).get(positionOfSymbolToBeLabeled)).setLabelDrawn(true);
//                ((GSymbol)dSymbolToBeLabeled).setLabelDrawn(true);
                EditableMathExpression expressionForHistory = (EditableMathExpression) ((EditableMathExpression)drawable).clone();
                addExpressionToHistory(expressionForHistory);
                txtF.setVisible(false);
                setUpNextLabeling();
                this.repaint();
            }
    }
}
